package com.bootdo.common.utils;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

public class WeiChartUtil{

    /**
     * 返回状态码
     */
    public static final String ReturnCode = "return_code";

    /**
     * 返回信息
     */
    public static final String ReturnMsg = "return_msg";

    /**
     * 业务结果
     */
    public static final String ResultCode = "result_code";

    /**
     * 预支付交易会话标识
     */
    public static final String PrepayId = "prepay_id";
    /**
     * 得到微信预付单的返回ID
     * @param orderId  商户自己的订单号
     * @param totalFee  总金额  （分）
     * @return
     */
    public static Map<String, String> getPreyId(String orderId,
                                                String totalFee){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", AuthUtil.APPID);
        reqMap.put("body", AuthUtil.BODY);
        reqMap.put("mch_id", AuthUtil.MchId);
        reqMap.put("nonce_str", getRandomString());
        reqMap.put("notify_url", AuthUtil.Notify_url); //通知地址
        //reqMap.put("detail", AuthUtil.subject); //非必填
        //reqMap.put("attach", "附加数据"); //非必填
        reqMap.put("out_trade_no", orderId); //商户系统内部的订单号,
        reqMap.put("spbill_create_ip", getHostIp()); //用户端实际ip
        reqMap.put("total_fee", String.valueOf(Double.parseDouble(totalFee)*100)); //订单总金额，单位为分
        // reqMap.put("time_start", "172.16.40.18"); //交易起始时间 非必填
        // reqMap.put("time_expire", "172.16.40.18"); //交易结束时间  非必填
        // reqMap.put("goods_tag", "172.16.40.18"); //商品标记 非必填
        reqMap.put("trade_type", "APP"); //交易类型
        //reqMap.put("limit_pay", "no_credit"); //指定支付方式,no_credit 指定不能使用信用卡支  非必填
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = HttpClientUtil.postHttplient(AuthUtil.PrepayUrl, reqStr);
        return getInfoByXml(retStr);
    }

    /**
     * 关闭订单
     * @param orderId  商户自己的订单号
     * @return
     */
    public static Map<String, String> closeOrder(String orderId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", AuthUtil.APPID);
        reqMap.put("mch_id", AuthUtil.MchId);
        reqMap.put("nonce_str", getRandomString());
        reqMap.put("out_trade_no", orderId); //商户系统内部的订单号,
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = HttpClientUtil.postHttplient(AuthUtil.CloseOrderUrl, reqStr);
        return getInfoByXml(retStr);
    }


    /**
     * 查询订单
     * @param orderId 商户自己的订单号
     * @return
     */
    public static String getOrder(String orderId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", AuthUtil.APPID);
        reqMap.put("mch_id", AuthUtil.MchId);
        reqMap.put("nonce_str", getRandomString());
        reqMap.put("out_trade_no", orderId); //商户系统内部的订单号,
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = HttpClientUtil.postHttplient(AuthUtil.OrderUrl, reqStr);
        return retStr;
    }


    /**
     * 退款
     * @param orderId  商户订单号
     * @param refundId  退款单号
     * @param totralFee 总金额（分）
     * @param refundFee 退款金额（分）
     * @param opUserId 操作员ID
     * @return
     */
    public static Map<String, String> refundWei(String orderId,String refundId,String totralFee,String refundFee,String opUserId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", AuthUtil.APPID);
        reqMap.put("mch_id", AuthUtil.MchId);
        reqMap.put("nonce_str", getRandomString());
        reqMap.put("out_trade_no", orderId); //商户系统内部的订单号,
        reqMap.put("out_refund_no", refundId); //商户退款单号
        reqMap.put("total_fee", totralFee); //总金额
        reqMap.put("refund_fee", refundFee); //退款金额
        reqMap.put("op_user_id", opUserId); //操作员
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = "";
        try{
            retStr = HttpClientUtil.postHttplientNeedSSL(AuthUtil.RefundUrl, reqStr, AuthUtil.refund_file_path, AuthUtil.MchId);
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
        return getInfoByXml(retStr);
    }


    /**
     * 退款查询
     * @param refundId  退款单号
     * @return
     */
    public static Map<String, String> getRefundWeiInfo(String refundId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", AuthUtil.APPID);
        reqMap.put("mch_id", AuthUtil.MchId);
        reqMap.put("nonce_str", getRandomString());
        reqMap.put("out_refund_no", refundId); //商户退款单号
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = HttpClientUtil.postHttplient(AuthUtil.RefundQueryUrl, reqStr);
        return getInfoByXml(retStr);
    }
    /**
     * 传入map  生成头为XML的xml字符串，例：<xml><key>123</key></xml>
     * @param map
     * @return
     */
    public static String creatXml(Map<String, String> map){
        StringBuffer sb = new StringBuffer("");
        sb.append("<xml>");

        Set<String> set = map.keySet();
        for(Iterator<String> it = set.iterator(); it.hasNext();){
            String key = it.next();
            Object value = map.get(key);
            sb.append("<").append(key).append(">");
            sb.append(value);
            sb.append("</").append(key).append(">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 得到加密值
     * @param map
     * @return
     */
    public static String getSign(Map<String, String> map){
        String[] keys = map.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        StringBuffer reqStr = new StringBuffer();
        for(String key : keys){
            String v = String.valueOf(map.get(key));
            if(v != null && !v.equals("")){
                reqStr.append(key).append("=").append(v).append("&");
            }
        }
        reqStr.append("key").append("=").append(AuthUtil.PAYKEY);

        //MD5加密
        return MD5Utils.encrypt(reqStr.toString()).toUpperCase();
    }

    /**
     * 得到10 位的时间戳
     * 如果在JAVA上转换为时间要在后面补上三个0
     * @return
     */
    public static String getTenTimes(){
        String t = new Date().getTime()+"";
        t = t.substring(0, t.length()-3);
        return t;
    }

    /**
     * 得到随机字符串
     * @return
     */
    public static String getRandomString(){
        int length = 32;
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < length; ++i){
            int number = random.nextInt(62);//[0,62)
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 得到本地机器的IP
     * @return
     */
    private static String getHostIp(){
        String ip = "";
        try{
            ip = InetAddress.getLocalHost().getHostAddress();
        }catch(UnknownHostException e){
            e.printStackTrace();
        }
        return ip;
    }

    /**
     * 将XML转换为Map 验证加密算法 然后返回
     * @param xmlStr
     * @return
     */
    public static Map<String, String>  getInfoByXml(String xmlStr){
        Map<String,String> map = new HashMap<String,String>();
        try{
            map = parseXmlStr(xmlStr);
            //对返回结果做校验.去除sign 字段再去加密
            String retSign = String.valueOf(map.get("sign"));
            map.remove("sign");
            String rightSing = getSign(map);
            if(rightSing.equals(retSign)){
                return map;
            }
        }catch(Exception e){
            return null;
        }
        return null;
    }

    /**
     * 解析XML字符串
     *
     * @param xml
     * @return
     * @throws DocumentException
     */
    private static Map<String, String> parseXmlStr(String xml)
            throws DocumentException {
        Document document = DocumentHelper.parseText(xml);
        Element root = document.getRootElement();
        return parseElement(root);
    }



    private static final String HEAD = "head";
    private static final String BODY = "body";
    /**
     * 解析Element
     *
     * @param root
     * @return
     */
    @SuppressWarnings("unchecked")
    private static Map<String, String> parseElement(Element root) {
        String rootName = root.getName();
        Iterator<Element> rootItor = root.elementIterator();
        Map<String, String> rMap = new HashMap<>();
        List<Map<String, String>> rList = new ArrayList<>();
        Map<String, String> rsltMap = null;
        while (rootItor.hasNext()) {
            Element tmpElement = rootItor.next();
            String name = tmpElement.getName();
            if (rsltMap == null || (!HEAD.equals(name) && !BODY.equals(name)
                    && !tmpElement.isTextOnly())) {
                if (!HEAD.equals(name) && !BODY.equals(name)
                        && !tmpElement.isTextOnly() && rsltMap != null) {
                    rList.add(rsltMap);
                }
                rsltMap = new HashMap<>();
            }
            if (!tmpElement.isTextOnly()) {
                Iterator<Element> headItor = tmpElement.elementIterator();
                while (headItor.hasNext()) {
                    Element hElement = headItor.next();
                    if (hElement.isTextOnly()) {
                        rsltMap.put(hElement.getName(), hElement.getTextTrim());
                    } else {
                        rsltMap.putAll(parseElement(hElement));
                    }
                }
            }
        }
        rList.add(rsltMap);
        rMap.put(rootName, String.valueOf(rList));
        return rMap;
    }


    /**
     * 将金额转换成分
     * @param fee 元格式的
     * @return 分
     */
    public static String changeToFen(Double fee){
        String priceStr = "";
        if(fee != null){
            int p = (int)(fee * 100); //价格变为分
            priceStr = Integer.toString(p);
        }
        return priceStr;
    }

}